﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.IdentityModel.Tokens;
using Microsoft.IdentityModel.Web;
using System.Collections.ObjectModel;
using Microsoft.IdentityModel;
using System.IdentityModel.Tokens;
using System.Xml;
using System.Security.Cryptography;
using System.Configuration;

namespace AccessControlLibrary
{
    /// <summary>
    /// SessionSecurityTokenHandler with 3DES cookie encryption
    /// </summary>
    /// <remarks>
    /// <p>The encryption key and initialization vector must be specified in base64 encoded strings in a custom configuration element :<br/ >
    /// &lt;tripleDES key="..." vector="..." /&gt;. See sample code.</p>
    /// <p>By default the SessionSecurityTokenHandler in the Geneva Framework uses the DPAPI to encrypt the cookies. This API gets an encryption key from the 
    /// current user's profile, which throws a FileNotFoundException under Windows Azure. Even if it works, since the key is user specific, it would probably
    /// not have been usable with multiple instances (unless the same user account is used for the services in all the servers, but I don't think you should
    /// make this kind of assumption anyway...)</p>
    /// </remarks>
    class TripleDESSessionSecurityTokenHandler : SessionSecurityTokenHandler
    {
        /// <summary>
        /// Default constructor
        /// </summary>
        /// <remarks>
        /// This constructor throws an exception because the encryption key and initialization vector must be specified in a custom configuration element.
        /// TEMPORARY FIX : Gets configuration form AppSettings
        /// </remarks>
        public TripleDESSessionSecurityTokenHandler()
            : base(GetDefaultTransforms(), DefaultTokenCache, DefaultSaveBootstrapTokens, DefaultTokenLifetime)
        {
            //throw new InvalidOperationException("The TripleDESSessionSecurityTokenHandler class expects the following config element <tripleDES key=\"...\" vector=\"...\">.");
        }

        /// <summary>
        /// Constructor with custom configuration element
        /// </summary>
        /// <param name="customConfigElement">Custom configuration element</param>
        /// <remarks>This constructors calls the base class constructors specifying a modified CookieTransform collection : in the default CookieTransform collection
        /// in the SessionSecurityTokenHandler class contained a DeflateCookieTransform and a ProtectedCookieTransform. Since the latter cannot be used in 
        /// the default</remarks>
        public TripleDESSessionSecurityTokenHandler(XmlElement customConfigElement)
            : base(GetDefaultTransforms(customConfigElement), DefaultTokenCache, DefaultSaveBootstrapTokens, DefaultTokenLifetime)
        {
        }

        /// <summary>
        /// Creates the default CookieTransform collection
        /// </summary>
        /// <returns></returns>
        /// <remarks>This method is used as a parameter to call the base class's constructors. All configuration values are controlled in this method</remarks>
        private static ReadOnlyCollection<CookieTransform> GetDefaultTransforms()
        {
            // Checks the custom configuration has the correct name and attributes
            if (ConfigurationManager.AppSettings["tripleDESKey"] == null)
            {
                throw new InvalidOperationException("The TripleDESSessionSecurityTokenHandler class expects the following application setting <add key=\"tripleDESKey\" value=\"...\" />.");
            }
            if (ConfigurationManager.AppSettings["tripleDESVector"] == null)
            {
                throw new InvalidOperationException("The TripleDESSessionSecurityTokenHandler class expects the following application setting <add key=\"tripleDESVector\" value=\"...\" />.");
            }

            // Reads the encryption key from the configuration file (and checks it is a valid key)
            byte[] key;
            try
            {
                key = Convert.FromBase64String(ConfigurationManager.AppSettings["tripleDESKey"]);
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("The key attribute for the TripleDESSessionSecurityTokenHandler is invalid : it must contain a base64 encoded 3DES encryption key.", ex);
            }
            if (key.Length != 16 && key.Length != 24)
            {
                throw new InvalidOperationException("The key attribute for the TripleDESSessionSecurityTokenHandler is invalid : the key length must be 16 or 24 bytes.");
            }

            // Reads the initialization vector from the configuration file (and checks it is a valid initialization vector)
            byte[] vector;
            try
            {
                vector = Convert.FromBase64String(ConfigurationManager.AppSettings["tripleDESVector"]);
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("The vector attribute for the TripleDESSessionSecurityTokenHandler is invalid : it must contain a base64 encoded 3DES initialisation vector.", ex);
            }
            if (vector.Length != 8)
            {
                throw new InvalidOperationException("The vector attribute for the TripleDESSessionSecurityTokenHandler is invalid : the initialisation vector length must be 8 bytes.");
            }

            // Initialize the TripleDESCookieTransform and returns the CookieTransfom collection
            TripleDESCookieTransform tripleDESTransform;
            try
            {
                tripleDESTransform = new TripleDESCookieTransform(key, vector);
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("TripleDESSessionSecurityTokenHandler initialisation failed with the following error message :\n"
                    + ex.Message + "\nCheck the inner exception for details.", ex);
            }
            return new List<CookieTransform>(new CookieTransform[] { new DeflateCookieTransform(), tripleDESTransform }).AsReadOnly();
        }

        /// <summary>
        /// Creates the default CookieTransform collection
        /// </summary>
        /// <param name="customConfigElement">Custom configuration element in which the 3DES encryption key and initialization vector is specified</param>
        /// <returns></returns>
        /// <remarks>This method is used as a parameter to call the base class's constructors. All configuration values are controlled in this method</remarks>
        private static ReadOnlyCollection<CookieTransform> GetDefaultTransforms(XmlElement customConfigElement)
        {
            // Checks the custom configuration has the correct name and attributes
            if (customConfigElement.LocalName != "tripleDES")
            {
                throw new InvalidOperationException("The TripleDESSessionSecurityTokenHandler class expects the following config element <tripleDES key=\"...\" vector=\"...\">. The custom config element is named" + customConfigElement.Name + ".");
            }
            if (customConfigElement.Attributes["key"] == null)
            {
                throw new InvalidOperationException("The TripleDESSessionSecurityTokenHandler class expects the following config element <tripleDES key=\"...\" vector=\"...\">. The key attribute is missing.");
            }
            if (customConfigElement.Attributes["vector"] == null)
            {
                throw new InvalidOperationException("The TripleDESSessionSecurityTokenHandler class expects the following config element <tripleDES key=\"...\" vector=\"...\">. The key attribute is missing.");
            }

            // Reads the encryption key from the configuration file (and checks it is a valid key)
            byte[] key;
            try 
            {
                key = Convert.FromBase64String(customConfigElement.Attributes["key"].Value);
            }
            catch (Exception ex)
            {
                throw  new InvalidOperationException("The key attribute for the TripleDESSessionSecurityTokenHandler is invalid : it must contain a base64 encoded 3DES encryption key.", ex);
            }
            if (key.Length != 16 && key.Length != 24)
            {
                throw new InvalidOperationException("The key attribute for the TripleDESSessionSecurityTokenHandler is invalid : the key length must be 16 or 24 bytes.");
            }

            // Reads the initialization vector from the configuration file (and checks it is a valid initialization vector)
            byte[] vector;
            try
            {
                vector = Convert.FromBase64String(customConfigElement.Attributes["vector"].Value);
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("The vector attribute for the TripleDESSessionSecurityTokenHandler is invalid : it must contain a base64 encoded 3DES initialisation vector.", ex);
            }
            if (vector.Length != 8)
            {
                throw new InvalidOperationException("The vector attribute for the TripleDESSessionSecurityTokenHandler is invalid : the initialisation vector length must be 8 bytes.");
            }

            // Initialize the TripleDESCookieTransform and returns the CookieTransfom collection
            TripleDESCookieTransform tripleDESTransform;
            try
            {
                tripleDESTransform = new TripleDESCookieTransform(key, vector);
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("TripleDESSessionSecurityTokenHandler initialisation failed with the following error message :\n"
                    + ex.Message + "\nCheck the inner exception for details.", ex);
            }
            return new List<CookieTransform>(new CookieTransform[] { new DeflateCookieTransform(), tripleDESTransform }).AsReadOnly();
        }
    }
}
